/****************************************************************************
 * Copyright (c) 2008 Mustafa K. Isik and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Mustafa K. Isik - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/

package org.eclipse.ecf.internal.sync.doc.cola;

import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.internal.sync.Activator;
import org.eclipse.ecf.internal.sync.SyncDebugOptions;
import org.eclipse.ecf.sync.doc.DocumentChangeMessage;

public class ColaInsertionTransformationStategy implements ColaTransformationStrategy {

	private static final long serialVersionUID = 5192625383622519749L;
	private static ColaInsertionTransformationStategy INSTANCE;

	private ColaInsertionTransformationStategy() {
		// default constructor is private to enforce singleton property via
		// static factory method
	}

	public static ColaTransformationStrategy getInstance() {
		if (INSTANCE == null) {
			INSTANCE = new ColaInsertionTransformationStategy();
		}
		return INSTANCE;
	}

	/**
	 * Resolves two conflicting <code>ColaDocumentChangeMessage</code>s by
	 * applying an appropriate operational transform.
	 * 
	 * If necessary, modifies <code>localAppliedMsg</code> as well to reflect
	 * knowledge of <code>remoteIncomingMsg</code> in case more
	 * conflicting/further diverging remote messages arrive.
	 * 
	 * @param remoteIncomingMsg message originating from remote site, generated
	 * on same document state as <code>localAppliedMsg</code>
	 * @param localAppliedMsg message already applied to local document,
	 * generation state corresponds to that of <code>remoteIncomingMsg</code>
	 * @param localMsgHighPrio determines insertion preference for same offsets,
	 * if true localAppliedMsg comes first
	 * @return operational transform of remote message, not conflicting with
	 * applied local message
	 */
	public ColaDocumentChangeMessage getOperationalTransform(ColaDocumentChangeMessage remoteIncomingMsg,
			ColaDocumentChangeMessage localAppliedMsg, boolean localMsgHighPrio) {

		Trace.entering(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_ENTERING, this.getClass(),
				"getOperationalTransform", //$NON-NLS-1$
				new Object[] { remoteIncomingMsg, localAppliedMsg, Boolean.valueOf(localMsgHighPrio) });

		final ColaDocumentChangeMessage remoteTransformedMsg = remoteIncomingMsg;

		if (localAppliedMsg.isInsertion()) {

			if (remoteTransformedMsg.getOffset() < localAppliedMsg.getOffset()) {
				// coopt(remote(low),local(high)) --> (remote(low),local(low +
				// high))
				localAppliedMsg.setOffset(localAppliedMsg.getOffset() + remoteTransformedMsg.getOffset());
			} else if (remoteTransformedMsg.getOffset() == localAppliedMsg.getOffset()) {
				// coopt(remote(same),local(same))
				if (localMsgHighPrio) {
					// at owner --> (remote(high),local(same))
					remoteTransformedMsg
							.setOffset(remoteTransformedMsg.getOffset() + localAppliedMsg.getText().length());
				} else {
					// at participant --> (remote(same),local(high))
					localAppliedMsg.setOffset(localAppliedMsg.getOffset() + remoteTransformedMsg.getText().length());
				}
			} else if (remoteTransformedMsg.getOffset() > localAppliedMsg.getOffset()) {
				// coopt(remote(high),local(low)) --> (remote(low +
				// high),local(low))
				remoteTransformedMsg.setOffset(remoteTransformedMsg.getOffset() + localAppliedMsg.getText().length());
			}

		} else if (localAppliedMsg.isDeletion()) {

			if (remoteTransformedMsg.getOffset() <= localAppliedMsg.getOffset()) {

				localAppliedMsg.setOffset(localAppliedMsg.getOffset() + remoteTransformedMsg.getLengthOfInsertedText());

			} else if (remoteTransformedMsg.getOffset() > localAppliedMsg.getOffset()) {

				if (remoteTransformedMsg
						.getOffset() > (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) {

					remoteTransformedMsg
							.setOffset(remoteTransformedMsg.getOffset() - localAppliedMsg.getLengthOfReplacedText());
				} else if (remoteTransformedMsg
						.getOffset() <= (localAppliedMsg.getOffset() + localAppliedMsg.getLengthOfReplacedText())) {

					// TODO test this ^#$%^#$ case
					final DocumentChangeMessage deletionFirstMessage = new DocumentChangeMessage(
							localAppliedMsg.getOffset(), remoteTransformedMsg.getOffset() - localAppliedMsg.getOffset(),
							localAppliedMsg.getText());
					final ColaDocumentChangeMessage deletionFirstPart = new ColaDocumentChangeMessage(
							deletionFirstMessage, localAppliedMsg.getLocalOperationsCount(),
							localAppliedMsg.getRemoteOperationsCount());
					localAppliedMsg.addToSplitUpRepresentation(deletionFirstPart);

					final DocumentChangeMessage deletionSecondMessage = new DocumentChangeMessage(
							localAppliedMsg.getOffset() + remoteTransformedMsg.getLengthOfInsertedText(),
							localAppliedMsg.getLengthOfReplacedText() - deletionFirstPart.getLengthOfReplacedText(),
							localAppliedMsg.getText());
					final ColaDocumentChangeMessage deletionSecondPart = new ColaDocumentChangeMessage(
							deletionSecondMessage, localAppliedMsg.getLocalOperationsCount(),
							localAppliedMsg.getRemoteOperationsCount());
					localAppliedMsg.addToSplitUpRepresentation(deletionSecondPart);

					localAppliedMsg.setSplitUp(true);

					remoteTransformedMsg.setOffset(localAppliedMsg.getOffset());

				}
			}
		}

		Trace.exiting(Activator.PLUGIN_ID, SyncDebugOptions.METHODS_EXITING, this.getClass(), "getOperationalTransform", //$NON-NLS-1$
				remoteTransformedMsg);
		return remoteTransformedMsg;
	}
}
